﻿/*
ParallelPlatform Project - A web platform to distribute and share knowledge
Online Editor - An online tool to create and play animations
Copyright (C) 2011 Daniel Guerrero Martínez & Sergio Rodríguez Lumley

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package Classes{

	import flash.display.MovieClip;
	import flash.events.MouseEvent;
	import flash.events.KeyboardEvent;
	import flash.events.Event;
	import flash.ui.Keyboard;
	import flash.ui.Mouse;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.text.TextField;
	import flash.text.TextFormat;
	import flash.text.AntiAliasType;
	import flash.text.TextFieldType;
	import flash.text.TextFieldAutoSize;
	import flash.utils.getQualifiedClassName;

	public class CanvasSelectHandler implements CanvasHandlerInterface {
		private var backSelectionRect:	MovieClip;
		private var cornerSelectionRect:MovieClip;
		private var editingLines:		MovieClip;
		private var selectionBackup:	Array;
		private var lastSelectedShape:	ShapeObject;
		private var doNotUnselectShape:	Boolean; // If last selected shape was not selected before, we dont want it to be unselected uppon mouse release
		private var dragging:			Boolean; // If user is dragging objects
		private var rotatingShapes:		Boolean; // Mouse is being hold while shapes are selected (used to rotate)
		private var scalingShapes:		Boolean; 
		private var showDepthTool:		Boolean;
		private var activated:			Boolean; // Indicates if this handler has been activated
		private var draggOffsetX:		Number;
		private var draggOffsetY:		Number;
		private var editingText:		Boolean;
		private var depthWidget:		DepthWidget; // Depth tool widget
		private var copyWidget:			CopyWidget;
		private var rotateCursor:		CursorRotate;
		private var scaleCursor:		CursorScale;
		private var tfield:				TextField;
		private var clipboard:			Array; // Array of ShapeObjects copied in clipboard
		
		private var core:				CanvasCore;
		
		/**
		 Constructor
		*/
		public function CanvasSelectHandler(c:CanvasCore){
			backSelectionRect	= new MovieClip();
			cornerSelectionRect	= new MovieClip();
			editingLines		= new MovieClip();
			depthWidget 	= new DepthWidget();
			copyWidget		= new CopyWidget();
			rotateCursor	= new CursorRotate();
			scaleCursor		= new CursorScale();
			tfield			= new TextField();
			clipboard		= new Array;
			dragging 		= false;
			draggOffsetX 	= 0;
			draggOffsetY 	= 0;
			rotatingShapes	= false;
			scalingShapes	= false;
			editingText 	= false;
			showDepthTool	= false;
			doNotUnselectShape 	= false;
			
			// TextField data
			var tformat:TextFormat 	= new TextFormat();
			tformat.align 	= "left";
			tformat.font	= (new Font_Sans()).fontName;
			tformat.size	= 16; // Default size for Text
			tformat.bold	= true;
			tformat.color	= 0xFFAA00; // Default color for Text
			
			tfield.type				= TextFieldType.DYNAMIC;
			tfield.multiline		= false;
			tfield.wordWrap			= false;
			tfield.selectable		= false;
			tfield.embedFonts		= true;
			tfield.antiAliasType 	= AntiAliasType.ADVANCED;
			tfield.autoSize 		= TextFieldAutoSize.LEFT;
			tfield.defaultTextFormat = tformat; // Assign the text format
			
			backSelectionRect.alpha 		= 0.5;
			scaleCursor.mouseEnabled		= false;
			rotateCursor.mouseEnabled 		= false;
			cornerSelectionRect.alpha 		= 0.5;
			rotateCursor.x 	= -1000; // Take out of stage
			scaleCursor.x 	= -1000; // Take out of stage
			depthWidget.x 	= 350;
			depthWidget.y 	= -30;
			depthWidget.setParentFunction(setDepthOfSelectedShapes);
			
			copyWidget.y	= -30;
			copyWidget.x	= depthWidget.x - depthWidget.width/2 - copyWidget.width;
			copyWidget.getCopyButton().addEventListener(MouseEvent.CLICK, copyEvent);
			copyWidget.getPasteButton().addEventListener(MouseEvent.CLICK, pasteEvent);
			
			core = c;
			
			core.addWidget(copyWidget);
			core.addWidget(depthWidget);
		}
		
		/**
		  Selection has been requested, we assume what we really want to do
		  is to edit the last inserted tool
		*/
		public function requestSelection():void{
			this.removeSelections();
			this.addShapeToSelection(core.getContainer(core.getCurrent()).getShapeAt(core.getContainer(core.getCurrent()).getNumShapes()-1));
			this.clickShape(null, core.getContainer(core.getCurrent()).getShapeAt(core.getContainer(core.getCurrent()).getNumShapes()-1), ShapeObject.DOUBLECLICK);
		}
		
		public function activate(param = null):void{
			if(! activated){
				activated = true;
				core.showBlockPanel(false);
				copyWidget.y = 0;
				//core.addEventListener(KeyboardEvent.KEY_DOWN, keyPressed);
				if(core.getSelectedShapes().length > 0){
					this.activateSelection();
				}
			}
		}
		
		public function deactivate():void{
			if(activated){
				activated = false;
				commitTextEditing();
				copyWidget.y = -30;
				if(core.getSelectedShapes().length > 0){
					this.deactivateSelection();
					showDepthTool = false;
				}
				//core.removeEventListener(KeyboardEvent.KEY_DOWN, keyPressed);
			}
		}
		
		public function checkUndoRedo():void{
			
		}
		
		public function clickShape(evt:MouseEvent, obj:ShapeObject, type:int):void{
			switch(type){
				case ShapeObject.SINGLECLICK:
					shapeSelect(evt, obj);
					break;
				case ShapeObject.DOUBLECLICK:
					if(! obj.isRemoved()){
						if(obj.hasText()){
							shapeEdit(evt, obj);
						}
					}else{
						obj.unremove();
						core.addAction(
							new Action(Action.INVALIDATE_REMOVE, obj.getFullID())
							);
					}
					break;
				case ShapeObject.MOUSEDOWN:
					shapeMouseDown(evt, obj);
					break;
			}
		}
		
		public function clickFront(evt:MouseEvent):void{
			if(editingText){
				commitTextEditing();
			}
		}
		
		public function clickBack(evt:MouseEvent):void{
			if(doNotUnselectShape){ // We are scaling or rotating selected
				doNotUnselectShape = false
			}else{
				if(! evt.shiftKey)
					this.removeSelections();
			}
		}
		
		/**
		  Indicates if this handler keeps a record of the actions it is doing
		*/
		public function hasRecord():Boolean{
			return false;
		}
		
		/**
		  Detects when a key has been pressed
		*/
		public function keyPressed(evt:KeyboardEvent):void{
			if(!editingText){
				if(evt.keyCode == Keyboard.DELETE){
					if(core.getSelectedShapes().length >= 1){
						deleteSelectedShapes();
					}
				}else if(evt.ctrlKey){
					switch(String.fromCharCode(evt.charCode)){
						case 'c':
							copyEvent();
							break;
						case 'v':
							pasteEvent();
							break;
					}
				}
			}
		}
		
		
		public function mouseMove(evt:MouseEvent):void{
			if(dragging){ // Moving shapes from place
				selectedShapesMove(evt);
			}else if(rotatingShapes){ // Rotating shapes
				selectedShapesRotate(evt);
			}else if(scalingShapes){ // Scaling shapes
				selectedShapesScaling(evt);
			}
		}
		
		/**
		  MouseUp triggers before click events
		*/
		public function mouseUp(evt:MouseEvent):void{
			if(doNotUnselectShape){
				if(dragging){
					saveSelectedShapesMovement(evt);
				}else if(rotatingShapes){ // Scaling or rotating shapes
					saveSelectedShapesRotation(evt);
				}else if(scalingShapes){
					saveSelectedShapesScaling(evt);
				}
			}
			dragging 		= false;
			rotatingShapes 	= false;
			scalingShapes 	= false;
		}
		
		/**
		  Redoes the last undone action (if there is one)
		*/
		public function redo():void{
			
		}
		
		/**
		  Undoes the last undone action (if there is one)
		*/
		public function undo():void{
			
		}
		
		// ---------------- PRIVATE FUNCTIONS --------------------- //
		
		/**
		  This function should trigger when user begins to select shapes,
		  so the first selection done fires this function. It adds a listener
		  to detect mouse down to rotate and scale the selected group and adds
		  the main selection rectangle.
		*/
		private function activateSelection():void{
			//core.addChild(core.getMainSelectionRect());
			core.getBackPanel().addChild(backSelectionRect);
			core.getBackPanel().addChild(cornerSelectionRect);
			cornerSelectionRect.addEventListener(MouseEvent.MOUSE_DOWN, mainRectDown);
			cornerSelectionRect.addEventListener(MouseEvent.MOUSE_OVER, showScaleCursor);
			cornerSelectionRect.addEventListener(MouseEvent.MOUSE_OUT, hideScaleCursor);
			
			backSelectionRect.addEventListener(MouseEvent.MOUSE_DOWN, backRectDown);
			backSelectionRect.addEventListener(MouseEvent.MOUSE_OVER, showRotateCursor);
			backSelectionRect.addEventListener(MouseEvent.MOUSE_OUT, hideRotateCursor);
			core.addChild(rotateCursor);
			core.addChild(scaleCursor);
			this.refreshMainSelectionRect(false);
			copyWidget.activateCopyButton(true);
		}
		
		/**
		  Adds the specified shape to the selection list
		  @param obj ShapeObject being selected
		*/
		private function addShapeToSelection(obj:ShapeObject):void{
			core.addShapeToSelection(obj);
			this.refreshMainSelectionRect(false);
			if(core.getSelectedShapes().length == 1){
				this.activateSelection();
			}
			appearDepthTool();
		}
		
		/**
		  If necessary, makes appear the DepthTool, adding a periodic event
		*/
		private function appearDepthTool():void{
			if(! showDepthTool){
				showDepthTool = true;
				if(depthWidget.y <= -30){
					depthWidget.y = -29;
					depthWidget.addEventListener(Event.ENTER_FRAME, refreshDepthTool);
				}
			}
		}
		
		/**
		  Triggers with mouse_down event in back panel, only when
		  selected shapes exist
		*/
		private function backDown(evt:MouseEvent):void{
			doNotUnselectShape = false;
			trace("CanvasSelectHandler: backDown() -> Implement this!");
			if(! evt.shiftKey){ // If shift is not selected we want to replace current selections for the new ones
				//TODO implement this and add listeners for it!!
			}else{ // If shift is selected, we want to add more shapes to selection
				
			}
		}
		
		/**
		  When mouse down over backSelectionRect, the intention is to
		  rotate all selected shapes
		*/
		private function backRectDown(evt:MouseEvent):void{
			doNotUnselectShape = false;
			rotatingShapes 	= true;
			// We save the rotation of the last selected shape
			// to check in the end if there has been any
			// effective rotation
			draggOffsetX	= lastSelectedShape.rotation;
			draggOffsetY	= 0; // Rotation applied
			selectionBackup = new Array(core.getSelectedShapes().length);
			for(var i:uint = 0; i<core.getSelectedShapes().length; ++i){
				selectionBackup[i] = core.getSelectedShapes()[i].rotation;
			}
		}
		
		/**
		  Commits the changes done while editing text of a shape,
		  it hides the blockPanel, destroys the inputText and
		  sets the text to the shape being edited.
		  This method checks whether text is being edited, so it is
		  safe calling it even if it is not being edited
		  @param param KeyboardEvent parameter for event purposes (null by default)
		*/
		private function commitTextEditing(param=null):void{
			if(editingText){
				// Or no parameter, or the parameter is a KeyboardEvent and
				// it doesnt have Shift pressed and it is Enter key
				if(param == null ||
				  (param != null && // We assume it is a KeyboardEvent, otherwise, change this!
				  (param as KeyboardEvent).shiftKey &&
				  (param as KeyboardEvent).keyCode == Keyboard.ENTER)
				  ){
					if(core.getInputTxt().text != lastSelectedShape.getText()){
						
						core.setShapeText(lastSelectedShape, core.getInputTxt().text);
						
						if(core.getShapeShadow(lastSelectedShape) != null &&
							core.getShapeShadow(lastSelectedShape).getText() == core.getInputTxt().text){
							
							core.addAction(new Action(
								Action.INVALIDATE_PREV_ACTIONS,
								lastSelectedShape.getFullID(),
								core.invalidateActions(
										Action.CHANGE_TEXT,
										lastSelectedShape.getFullID()
										)));
						}
					}
					core.removeChild(core.getInputTxt());
					core.showBlockPanel(false);
					lastSelectedShape.showText();
					
					core.removeEventListener(KeyboardEvent.KEY_DOWN, commitTextEditing, true);
					//core.addEventListener(KeyboardEvent.KEY_DOWN, keyPressed); // Now we can restore our Key detector
					editingText = false;
				}
			}
		}
		
		/**
		  Copies the selected shapes to clipboard
		*/
		private function copyEvent(param = null):void{
			if(core.getSelectedShapes().length > 0){
				var sShapes:Vector.<ShapeObject> = core.getSelectedShapes();
				clipboard = new Array(sShapes.length);
				for(var i:uint = 0; i<sShapes.length; ++i){
					clipboard[i] = sShapes[i].duplicate();
				}
				copyWidget.activatePasteButton(true);
			}
		}
		
		/**
		  This function triggers when the last selected shape has been
		  unselected
		*/
		private function deactivateSelection():void{
			//core.removeChild(core.getMainSelectionRect());
			core.getBackPanel().removeChild(backSelectionRect);
			core.getBackPanel().removeChild(cornerSelectionRect);
			cornerSelectionRect.removeEventListener(MouseEvent.MOUSE_DOWN, mainRectDown);
			cornerSelectionRect.removeEventListener(MouseEvent.MOUSE_OVER, showScaleCursor);
			cornerSelectionRect.removeEventListener(MouseEvent.MOUSE_OUT, hideScaleCursor);
			
			backSelectionRect.removeEventListener(MouseEvent.MOUSE_DOWN, backRectDown);
			backSelectionRect.removeEventListener(MouseEvent.MOUSE_OVER, showRotateCursor);
			backSelectionRect.removeEventListener(MouseEvent.MOUSE_OUT, hideRotateCursor);

			hideScaleCursor(null);
			hideRotateCursor(null);
			core.removeChild(rotateCursor);
			core.removeChild(scaleCursor);
			copyWidget.activateCopyButton(false);
		}
		
		/**
		  Removes from stage all selected shapes
		*/
		private function deleteSelectedShapes():void{
			var openCluster:Boolean = true;
			for each(var obj:ShapeObject in core.getSelectedShapes()){
				// If the object was created on the same scene
				// we remove it completely
				if(obj.getStageID() == core.getCurrent()){
					core.getContainer(core.getCurrent()).removeShape(obj);
				}else{ // if not, we just mark it as deleted
					obj.remove();
				}
				
				//An object has been removed from scene
				//REMOVE, "fullID"
				core.addAction(new Action(
					Action.REMOVE,
					obj.getFullID() ), openCluster);
				
				openCluster = false;
			}
			this.removeSelections();
		}
		
		/**
		  Draws a line in the object's graphics from origin to destination, relative to object's center
		  @param obj MovieClip to draw a line to
		  @param origx Number X coordinate of the line's origin
		  @param origy Number Y coordinate of the line's origin
		  @param destx Number X coordinate of the line's end
		  @param desty Number Y coordinate of the line's end
		*/
		private function drawLine(obj:MovieClip, origx:Number, origy:Number, destx:Number, desty:Number):void{
			obj.graphics.moveTo(origx, origy);
			obj.graphics.lineTo(destx, desty);
		}
		
		/**
		  Hides the rotate cursor and shows original one
		*/
		private function hideRotateCursor(evt:MouseEvent):void{
			Mouse.show();
			rotateCursor.stopDrag();
			//rotateCursor.stopAnimation();
			rotateCursor.x = -1000; // Take out of stage
		}
		
		/**
		  Hides the scale cursor and shows original one
		*/
		private function hideScaleCursor(evt:MouseEvent):void{
			Mouse.show();
			scaleCursor.stopDrag();
			//scaleCursor.stopAnimation();
			scaleCursor.x = -1000; // Take out of stage
		}
		
		/**
		  When mouse down over mainSelectionRect, the intention is to
		  scale all selected shapes
		*/
		private function mainRectDown(evt:MouseEvent):void{
			doNotUnselectShape = false;
			scalingShapes 	= true;
			draggOffsetX	= 0;
			draggOffsetY	= 0;
			// We save all shape's scaling
			selectionBackup = new Array(core.getSelectedShapes().length+1);
			
			//var m:Number = (evt.stageY - core.getMainSelectionRect().y)/(evt.stageX - core.getMainSelectionRect().x);
			//selectionBackup[0] = new Point(evt.stageX, evt.stageY); // We save in the first position the point as reference
			selectionBackup[0] = new Point(core.getMainSelectionRect().x + core.getMainSelectionRect().width/2,
											core.getMainSelectionRect().y + core.getMainSelectionRect().height/2); // We save in the first position the point as reference
			for(var i:uint = 0; i<core.getSelectedShapes().length; ++i){
				selectionBackup[i+1] = new Point(core.getSelectedShapes()[i].scaleX, core.getSelectedShapes()[i].scaleY);
			}
		}
		
		/**
		  Creates a copy of shape and pastes it in the stage
		  @param shape ShapeObject to paste
		  @param first Boolean indicating if its the first shape to paste
		  @return ShapeObject pasted
		*/
		private function paste(orig:ShapeObject, first:Boolean):ShapeObject{
			var shape:ShapeObject = core.addShapeObjXY(orig.x, orig.y, getQualifiedClassName(orig), first);
			if(orig.hasText()){
				core.setShapeText(shape, orig.getText(), false);
			}
			if(orig.getLines() != null){
				core.setShapePaint(shape, orig.getLines(), orig.getColors(), false);
			}
			if(orig.getBlur().x != 0 || orig.getBlur().y != 0){
				core.blurShape(shape, orig.getBlur().x, orig.getBlur().y, true, false);
			}
			if(orig.rotation != 0){
				core.rotateShape(shape, orig.rotation, true, false);
			}
			if(orig.scaleX != 1 || orig.scaleY != 1){
				core.scaleShape(shape, orig.scaleX, orig.scaleY, true, false);
			}
			return shape;
		}
		
		/**
		  Pastes the clipboard in the stage
		*/
		private function pasteEvent(param = null):void{
			if(clipboard.length > 0){
				var first:Boolean = true;
				
				this.removeSelections();
				for each(var shape:ShapeObject in clipboard){
					this.addShapeToSelection(paste(shape, first));
					first = false;
				}
			}
		}
		
		/**
		  Refreshes the DepthTool animation, when it has to be shown no more, it autoremoves the event
		  @param evt EnterFrame event
		*/
		private function refreshDepthTool(evt:Event):void{
			if(showDepthTool){
				depthWidget.y = Math.min(1, depthWidget.y+3);
			}else{
				depthWidget.y = Math.max(-30, depthWidget.y-2);
				if(depthWidget.y <= -30){
					depthWidget.removeEventListener(Event.ENTER_FRAME, refreshDepthTool);
				}
			}
		}
		
		/**
		  Refreshes the size of the main selection rect according to the number of
		  selection rects
		  @param propagate Boolean indicating to propagate refreshment to core or not
		*/
		private function refreshMainSelectionRect(propagate:Boolean = true):void{
			if(core.getSelectionRects().length > 0){
				if(propagate) core.refreshMainSelectionRect();
				
				backSelectionRect.x = core.getMainSelectionRect().x;
				backSelectionRect.y = core.getMainSelectionRect().y;
				backSelectionRect.graphics.clear();
				backSelectionRect.graphics.beginFill(0xFFFFFF, 0);
				//backSelectionRect.graphics.lineStyle(60,0xFFFFFF);
				backSelectionRect.graphics.drawRect(
						-core.getMainSelectionRect().width/2-30,
						-core.getMainSelectionRect().height/2-30,
						 core.getMainSelectionRect().width+60,
						 core.getMainSelectionRect().height+60);
				backSelectionRect.graphics.endFill();
				
				cornerSelectionRect.x = core.getMainSelectionRect().x;
				cornerSelectionRect.y = core.getMainSelectionRect().y;
				cornerSelectionRect.graphics.clear();
				cornerSelectionRect.graphics.beginFill(0xFFAA00, 0.7);
				cornerSelectionRect.graphics.drawRect(-core.getMainSelectionRect().width/2-20, -core.getMainSelectionRect().height/2-20, 40, 40);
				cornerSelectionRect.graphics.drawRect( core.getMainSelectionRect().width/2-20, -core.getMainSelectionRect().height/2-20, 40, 40);
				cornerSelectionRect.graphics.drawRect(-core.getMainSelectionRect().width/2-20,  core.getMainSelectionRect().height/2-20, 40, 40);
				cornerSelectionRect.graphics.drawRect( core.getMainSelectionRect().width/2-20,  core.getMainSelectionRect().height/2-20, 40, 40);
				cornerSelectionRect.graphics.endFill();
			}
		}
		
		/**
		 Removes all the selections over the objects (SelectionRect)
		*/
		private function removeSelections():void{
			if(core.getSelectedShapes().length > 0){
				this.deactivateSelection();
			}
			core.removeSelections();
			showDepthTool = false;
		}
		
		/**
		  Auxiliar function which can remove the object specified
		  from both selection vectors (selectedShapes and selectionRects)
		  Obj must be part of selectedShapes, an error will occur otherwise
		  @param obj ShapeObject to remove from selection lists
		*/
		private function removeShapeFromSelection(obj:ShapeObject):void{
			core.removeShapeFromSelection(obj);
			if(core.getSelectionRects().length > 0){
				refreshMainSelectionRect(false);
			}else{
				this.deactivateSelection();
			}
		}
		
		/**
		  Saves the movement performed over selected shapes
		*/
		private function saveSelectedShapesMovement(evt:MouseEvent):void{
			dragging = false;
			if(draggOffsetX != 0 || draggOffsetY != 0){
				
				// If objects have been moved insted of fixed in it's old place
				for(var i:uint = 0; i<core.getSelectedShapes().length; ++i){
					//An object has been moved in scene
					// MOVE, "fullID", x_init, y_init, x_end, y_end
					core.addAction(new Action(
								Action.MOVE,
								core.getSelectedShapes()[i].getFullID(),
								core.getSelectedShapes()[i].x - draggOffsetX,
								core.getSelectedShapes()[i].y - draggOffsetY,
								core.getSelectedShapes()[i].x, core.getSelectedShapes()[i].y),
							(i == 0)
						);
				}
				if(core.getSelectedShapes().length == 1 &&
				   core.getShapeShadow(core.getSelectedShapes()[0]) != null &&
				   core.getShapeShadow(core.getSelectedShapes()[0]).x == core.getSelectedShapes()[0].x &&
				   core.getShapeShadow(core.getSelectedShapes()[0]).y == core.getSelectedShapes()[0].y){
					// Ok, if we get here is because THE only object we were moving
					// we are putting it back where it was before
					// So now we proceed invalidating all other actions
					var array:Array = core.invalidateActions(Action.MOVE, core.getSelectedShapes()[0].getFullID());
					if(array != null){
						core.addAction(new Action(
							Action.INVALIDATE_PREV_ACTIONS,
							core.getSelectedShapes()[0].getFullID(),
							array), false
							);
					}
				}
			}
			draggOffsetX = 0;
			draggOffsetY = 0;
		}
		
		/**
		  Saves the rotation performed over selected shapes
		*/
		private function saveSelectedShapesRotation(evt:MouseEvent):void{
			rotatingShapes = false;
			var rot:Number = draggOffsetY;
			
			// If there has been any effective rotation
			if(draggOffsetX != rot){
				
				// We rotate all objects
				for(var i:uint = 0; i<core.getSelectedShapes().length; ++i){
					core.getSelectedShapes()[i].rotation = selectionBackup[i]; // We restore the rotation as before
					core.rotateShape(core.getSelectedShapes()[i], rot, true, // Save this action
							(i == 0) // First action of the cluster
						);
					core.getSelectionRects()[i].rotation = rot;
				}
				
				if(core.getSelectedShapes().length == 1 &&
				   core.getShapeShadow(core.getSelectedShapes()[0]) != null && // if it has shadow, then it was created before this frame
				   core.getShapeShadow(core.getSelectedShapes()[0]).rotation == core.getSelectedShapes()[0].rotation){
					// Ok, if we get here is because THE only object we were rotating
					// we are putting it back how it was before
					// So now we proceed invalidating all other rotating actions in this frame
					var array:Array = core.invalidateActions(Action.ROTATE, core.getSelectedShapes()[0].getFullID());
					if(array != null){
						core.addAction(new Action(
							Action.INVALIDATE_PREV_ACTIONS,
							core.getSelectedShapes()[0].getFullID(),
							array), false
							);
					}
				}
				
				refreshMainSelectionRect();
			}
			draggOffsetX = 0;
			draggOffsetY = 0;
			selectionBackup = new Array(); // We clear it's content
			core.removeChild(tfield);
			core.removeChild(editingLines);
		}
		
		/**
		  Saves the scaling performed over selected shapes
		*/
		private function saveSelectedShapesScaling(evt:MouseEvent):void{
			scalingShapes = false;
			
			// If there has been any effective scaling
			if(draggOffsetX != selectionBackup[0].x ||
				draggOffsetY != selectionBackup[0].y){
				
				// We rotate all objects
				for(var i:uint = 0; i<core.getSelectedShapes().length; ++i){
					core.getSelectedShapes()[i].scaleX = selectionBackup[i+1].x; // We restore the rotation as before
					core.getSelectedShapes()[i].scaleY = selectionBackup[i+1].y; // We restore the rotation as before
					core.scaleShape(core.getSelectedShapes()[i], draggOffsetX, draggOffsetY, true, // Save this action
							(i == 0) // First action of the cluster
						);
					core.getSelectionRects()[i].scaleX = draggOffsetX;
					core.getSelectionRects()[i].scaleY = draggOffsetY;
				}
				
				if(core.getSelectedShapes().length == 1 &&
				   core.getShapeShadow(core.getSelectedShapes()[0]) != null && // if it has shadow, then it was created before this frame
				   core.getShapeShadow(core.getSelectedShapes()[0]).scaleX == core.getSelectedShapes()[0].scaleX &&
				   core.getShapeShadow(core.getSelectedShapes()[0]).scaleY == core.getSelectedShapes()[0].scaleY){
					// Ok, if we get here is because THE only object we were rotating
					// we are putting it back how it was before
					// So now we proceed invalidating all other rotating actions in this frame
					var array:Array = core.invalidateActions(Action.SCALE, core.getSelectedShapes()[0].getFullID());
					if(array != null){
						core.addAction(new Action(
							Action.INVALIDATE_PREV_ACTIONS,
							core.getSelectedShapes()[0].getFullID(),
							array), false
							);
					}
				}
				
				refreshMainSelectionRect();
			}
			draggOffsetX = 0;
			draggOffsetY = 0;
			selectionBackup = new Array(); // We clear it's content
			core.removeChild(tfield);
			core.removeChild(editingLines);
		}
		
		/**
		  Incrementally move all selectedShapes where evt indicates
		*/
		private function selectedShapesMove(evt:MouseEvent):void{
			// Our center is in the last selectedShape center
			var offsetx:Number = evt.stageX - lastSelectedShape.x;
			var offsety:Number = evt.stageY - lastSelectedShape.y;
			
			if(core.getSelectedShapes().length == 1 &&
			   core.getShapeShadow(lastSelectedShape) != null &&
			   core.distanceOfNum(core.getShapeShadow(lastSelectedShape),
									evt.stageX,
									evt.stageY) < 20){
				offsetx = core.getShapeShadow(lastSelectedShape).x - lastSelectedShape.x;
				offsety = core.getShapeShadow(lastSelectedShape).y - lastSelectedShape.y;
			}
			
			draggOffsetX += offsetx; // Global movement from beginning to end
			draggOffsetY += offsety;
			
			// So we move all shapes to the current position of the mouse
			for(var i:uint = 0; i<core.getSelectedShapes().length; ++i){
				core.getSelectedShapes()[i].moveRelative(offsetx, offsety);
				core.getSelectionRects()[i].x += offsetx;
				core.getSelectionRects()[i].y += offsety;
			}
			//refreshMainSelectionRect(); // Instead of recalculating the whole mainSelectionRect, we just add the offset
			core.getMainSelectionRect().x += offsetx;
			core.getMainSelectionRect().y += offsety;
			backSelectionRect.x += offsetx;
			backSelectionRect.y += offsety;
			cornerSelectionRect.x += offsetx;
			cornerSelectionRect.y += offsety;
			
			core.repaint(); // Repaint background, all shadows and stuff
			doNotUnselectShape = true; // We are dragging, so dont unselect uppon mouse release!
		}
		
		/**
		  Rotate all selected shapes where evt indicates respect from
		  main-selection-rectangle's center
		*/
		private function selectedShapesRotate(evt:MouseEvent):void{
			var rot:Number = core.getAngle(core.getMainSelectionRect(), evt.stageX, evt.stageY);
			
			if(! doNotUnselectShape){ // This is the first time we start rotating
				core.addChild(editingLines);
				editingLines.x = core.getMainSelectionRect().x;
				editingLines.y = core.getMainSelectionRect().y;
				core.addChild(tfield);
			}
			
			if(core.getSelectedShapes().length == 1 &&
			   core.getShapeShadow(lastSelectedShape) != null){
				// Flash sometimes represents rotation with numbers bigger than 360,
				// even though you specify "rotation = 260", it may be saved by flash
				// as "rotation = 620", so we normalice this value between [0, 360)y
				var rot_shadow:Number = core.getShapeShadow(lastSelectedShape).rotation;
				if(rot_shadow < 0) rot_shadow = 360+rot_shadow;
				else if(rot_shadow >= 360){
				   while(rot_shadow >= 360){
					   rot_shadow = rot_shadow - 360;
				   }
				}
				
				if((Math.abs(rot_shadow - rot) < 15 || Math.abs(rot_shadow - rot) > 345)){
					rot = rot_shadow;
				}
			}
			
			// Rotate in 45º when shift key is pressed
			if(evt.shiftKey){
				rot = Math.round(rot/45)*45;
			}
			
			// So we rotate all shapes to the current rotation of the mouse respect from center
			for(var i:uint = 0; i<core.getSelectedShapes().length; ++i){
				core.rotateShape(core.getSelectedShapes()[i], rot);
				core.getSelectionRects()[i].rotation = rot;
			}
			
			tfield.text		= String(360-Math.round(rot))+"º";
			tfield.x		= evt.stageX+15;
			tfield.y		= evt.stageY+17;
			
			// We paint the editing lines
			editingLines.graphics.clear();
			editingLines.graphics.lineStyle(1, 0xFFAA00);
			editingLines.graphics.moveTo(Math.max(core.getMainSelectionRect().width, 100), 0);
			editingLines.graphics.lineTo(0, 0);
			editingLines.graphics.lineTo(Math.max(core.getMainSelectionRect().width, 100)*Math.cos(rot*Math.PI/180),
										 Math.max(core.getMainSelectionRect().width, 100)*Math.sin(rot*Math.PI/180));
			
			draggOffsetY 	= rot;
			doNotUnselectShape = true; // We are rotating, so dont unselect uppon mouse release!
		}
		
		/**
		  Scale all selected shapes where evt indicates respect from
		  main-selection-rectangle's center
		*/
		private function selectedShapesScaling(evt:MouseEvent):void{
			var mselec:MovieClip = core.getMainSelectionRect();
			var scalex:Number = Math.abs(evt.stageX - mselec.x)/Math.abs((selectionBackup[0] as Point).x - mselec.x);
			var scaley:Number = Math.abs(evt.stageY - mselec.y)/Math.abs((selectionBackup[0] as Point).y - mselec.y);
			
			if(! doNotUnselectShape){
				core.addChild(editingLines);
				editingLines.x = mselec.x;
				editingLines.y = mselec.y;
				core.addChild(tfield);
			}
			
			if(scalex > 0.9 && scalex < 1.1) scalex = 1;
			if(scaley > 0.9 && scaley < 1.1) scaley = 1;
			
			if(core.getSelectedShapes().length == 1 &&
			   core.getShapeShadow(lastSelectedShape) != null){
				if(Math.abs(core.getShapeShadow(lastSelectedShape).scaleX - scalex) < 0.15){
					scalex = core.getShapeShadow(lastSelectedShape).scaleX;
				}
				if(Math.abs(core.getShapeShadow(lastSelectedShape).scaleY - scaley) < 0.15){
					scaley = core.getShapeShadow(lastSelectedShape).scaleY;
				}
			}
			
			// If shift key is pressed, make the scaling square
			if(evt.shiftKey){
				if(scalex > scaley) scaley = scalex;
				else scalex = scaley;
			}
			
			for(var i:uint = 0; i<core.getSelectedShapes().length; ++i){
				core.scaleShape(core.getSelectedShapes()[i], scalex, scaley);
				core.getSelectionRects()[i].scaleX = scalex;
				core.getSelectionRects()[i].scaleY = scaley;
			}
			
			tfield.text		= "("+String(Math.round(scalex*100))+"%, "+String(Math.round(scaley*100))+"%)";
			tfield.x		= evt.stageX+15;
			tfield.y		= evt.stageY+17;
			
			// We paint the editing lines
			editingLines.graphics.clear();
			editingLines.graphics.lineStyle(1, 0xFFAA00);
			this.drawLine(editingLines, -mselec.width, -mselec.height, mselec.width,  mselec.height);
			this.drawLine(editingLines, -mselec.width,  mselec.height, mselec.width, -mselec.height);
			editingLines.graphics.lineStyle(Math.max((mselec.height/2)*0.1, (mselec.width/2)*0.1) , 0x77FF77, 0.3, false, "normal", flash.display.CapsStyle.NONE, flash.display.JointStyle.MITER);
			editingLines.graphics.drawRect(-mselec.width/2, -mselec.height/2, mselec.width, mselec.height);
			
			draggOffsetX = scalex;
			draggOffsetY = scaley;
			doNotUnselectShape = true; // We are scaling, so dont unselect uppon mouse release!
		}
		
		/**
		  Sets the depth of the current selected shapes as specified
		  @param type uint with the type of arrangement requested (overAll, overOne, underOne, underAll)
		*/
		private function setDepthOfSelectedShapes(type:uint):void{
			var vec:Vector.<uint>;
			var newDepth:uint;
			var i:uint;
			
			if(core.getSelectedShapes().length > 0){
			
			switch(type){
				case CDepth_Button.OVERALL:
					vec = core.getOrderedSelectedReferences(true); // Decreasing order
					newDepth = core.getContainer(core.getCurrent()).getNumShapes()-1;
					
					for(i = 0; i < vec.length; ++i){
						core.setShapeDepth(core.getSelectedShapes()[vec[i]], newDepth, (i==0));
						--newDepth;
					}
				
					break;
				case CDepth_Button.OVERONE:
					vec = core.getOrderedSelectedReferences(true); // Decreasing order
					i = 0;
					// First iteration to make sure we do not go out of limits
					newDepth = Math.min(core.getContainer(core.getCurrent()).getShapeDepth(core.getSelectedShapes()[vec[i]])+1, core.getContainer(core.getCurrent()).getNumShapes());
					core.setShapeDepth(core.getSelectedShapes()[vec[i]], newDepth, true);
					for(i = 1; i < vec.length; ++i){
						// Check if the next element of the ordered list is the same -1, then do not do that loop!
						if(core.getContainer(core.getCurrent()).getShapeDepth(core.getSelectedShapes()[vec[i-1]]) -
							core.getContainer(core.getCurrent()).getShapeDepth(core.getSelectedShapes()[vec[i]]) > 1){
							newDepth = core.getContainer(core.getCurrent()).getShapeDepth(core.getSelectedShapes()[vec[i]])+1;
							core.setShapeDepth(core.getSelectedShapes()[vec[i]], newDepth, false);
						}
					}
					
					break;
				case CDepth_Button.UNDERONE:
					vec = core.getOrderedSelectedReferences(false); // Increasing order
					
					i = 0;
					// First iteration to make sure we do not go out of limits
					newDepth = Math.max(core.getContainer(core.getCurrent()).getShapeDepth(core.getSelectedShapes()[vec[i]])-1, 0);
					core.setShapeDepth(core.getSelectedShapes()[vec[i]], newDepth, true);
					for(i = 1; i < vec.length; ++i){
						// Check if the next element of the ordered list is the same -1, then do not do that loop!
						if(core.getContainer(core.getCurrent()).getShapeDepth(core.getSelectedShapes()[vec[i]]) -
							core.getContainer(core.getCurrent()).getShapeDepth(core.getSelectedShapes()[vec[i-1]]) > 1){
							newDepth = core.getContainer(core.getCurrent()).getShapeDepth(core.getSelectedShapes()[vec[i]])-1;
							core.setShapeDepth(core.getSelectedShapes()[vec[i]], newDepth, false);
						}
					}
					break;
				case CDepth_Button.UNDERALL:
					vec = core.getOrderedSelectedReferences(false); // Increasing order
					newDepth = 0;
					
					for(i = 0; i < vec.length; ++i){
						core.setShapeDepth(core.getSelectedShapes()[vec[i]], newDepth, (i==0));
						++newDepth;
					}
					break;
			}}
		}
		
		/**
		  Edits the text of a selected shape
		  @param evt MouseEvent
		  @param obj ShapeObject which we are going to edit the text
		*/
		private function shapeEdit(evt:MouseEvent, obj:ShapeObject):void{
			obj.hideText();
			core.showBlockPanel(true);
			editingText = true;
			
			core.getInputTxt().setStyle("textFormat", obj.getTextFormat());
			core.getInputTxt().text 		= obj.getText();
			core.getInputTxt().height 	= Math.min(obj.getTextHeight()*1.1, obj.height);
			core.getInputTxt().width 	= obj.width;
			core.getInputTxt().x 		= obj.x-obj.width/2;
			core.getInputTxt().y 		= obj.y + obj.getTextY();
			
			// we set this Shape as the reference Shape, although it should already be from shapeMouseDown...
			lastSelectedShape = obj;
			core.addChild(core.getInputTxt());
			core.addEventListener(KeyboardEvent.KEY_DOWN, commitTextEditing, true);
			//core.removeEventListener(KeyboardEvent.KEY_DOWN, keyPressed); // We remove our key event listener to avoid deleting the object while writting
		}
				
		/**
		  Adds new shapes to the list of selected shapes and defines the last
		  selected shape when mouse is pressed over a Shape
		  @param evt MouseEvent
		  @param obj ShapeObject which has triggered the event
		*/
		private function shapeMouseDown(evt:MouseEvent, obj:ShapeObject):void{
			dragging = true;
			
			// if the object was not selected before
			if(core.getSelectedShapes().indexOf(obj) < 0){
				if(! evt.shiftKey) // If we are not adding more selections with shift
					this.removeSelections();
				this.addShapeToSelection(obj);
				
				// We are adding new shapes to selection now, dont unselect it uppon release!
				doNotUnselectShape = true;
			}
			
			// we set this Shape as the reference Shape
			lastSelectedShape = obj;
		}
		
		/**
		  Triggers the shape selection after a single click to an object,
		  behaviour depends on the tool selected, when select tool, we unselect
		  the shapes if necessary
		  @param evt MouseEvent
		  @param obj ShapeObject which triggered the event
		*/
		private function shapeSelect(evt:MouseEvent, obj:ShapeObject):void{
			if(! doNotUnselectShape){
				// if the object was selected before (check shapeMouseDown to see how the shape is added first)
				if(core.getSelectedShapes().indexOf(obj) >= 0){
					// The object was selected, if "shift" was pressed, unselect this one
					if(evt.shiftKey){
						this.removeShapeFromSelection(obj);
					}else{
						// if not, unselect the rest and select only this one
						this.removeSelections();
						this.addShapeToSelection(obj);
					}
				}
			}
			doNotUnselectShape = false;
		}
		
		/**
		  Hides the original cursor and shows the rotate one
		*/
		private function showRotateCursor(evt:MouseEvent):void{
			Mouse.hide();
			rotateCursor.startDrag(true);
			//rotateCursor.startAnimation();
		}
		
		/**
		  Hides the original cursor and shows the scale one
		*/
		private function showScaleCursor(evt:MouseEvent):void{
			Mouse.hide();
			scaleCursor.startDrag(true);
			//scaleCursor.startAnimation();
		}

	}
}